package ink.ykb.redisLockDemo.config.lock.redis;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import ink.ykb.redisLockDemo.config.CodeConstant;
import ink.ykb.redisLockDemo.config.MessageUtil;
import ink.ykb.redisLockDemo.config.ServiceException;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class RedisManagerService {
	
	
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	@Autowired
	@Qualifier("redisTemplate")
	private RedisTemplate<String,Object> redisTemplate;
	@Autowired
	private MessageUtil messageUtil;

	public String getHashTime(String key) {
		return this.stringRedisTemplate.getExpire(key, TimeUnit.SECONDS).toString();
	}

	public Boolean setHashOne(String key, String word, Object value, int time) {
		log.debug("放入redis：{} {} {} {}", new Object[]{key, word, value, Integer.valueOf(time)});
		boolean result = this.stringRedisTemplate.opsForHash().putIfAbsent(key, word, value).booleanValue();
		log.info("放入redis结果：{} {} {}", new Object[]{key, word, Boolean.valueOf(result)});
		return this.stringRedisTemplate.expire(key, (long) time, TimeUnit.SECONDS);
	}

	public Boolean setHashOne(String key, String word, Object value) {
		return this.stringRedisTemplate.opsForHash().putIfAbsent(key, word, value);
	}

	public Boolean justHashOneIfExist(String key, String word) {
		return this.stringRedisTemplate.opsForHash().hasKey(key, word);
	}

	public Long deleteHashOne(String key, String word) {
		return this.stringRedisTemplate.opsForHash().delete(key, new Object[]{word});
	}

	public Boolean setHashMany(String key, Map<String, Object> map, int time) {
		this.stringRedisTemplate.boundHashOps(key).putAll(map);
		return this.stringRedisTemplate.expire(key, (long) time, TimeUnit.SECONDS);
	}

	public Object getHashOne(String key, String word) {
		return this.stringRedisTemplate.boundHashOps(key).get(word);
	}

	public Object getHashMany(String key) {
		return this.stringRedisTemplate.boundHashOps(key).entries();
	}

	public void setStr(String key, String value) {
		this.stringRedisTemplate.opsForValue().set(key, value);
	}

	public void setStr(String key, String value, long expire) {
		this.stringRedisTemplate.opsForValue().set(key, value, expire, TimeUnit.MILLISECONDS);
	}

	public void setStr(String key, String value, long expire, TimeUnit timeUnit) {
		this.stringRedisTemplate.opsForValue().set(key, value, expire, timeUnit);
	}

	public String getStr(String key) {
		return (String) this.stringRedisTemplate.boundValueOps(key).get();
	}

	public String listRightPop(String key) {
		String result = (String) this.stringRedisTemplate.opsForList().rightPop(key);
		return result;
	}

	public String listLast(String key) {
		List result = this.stringRedisTemplate.opsForList().range(key, -1L, -1L);
		return result != null && result.size() > 0 ? (String) result.get(0) : null;
	}

	public Long listRightPush(String key, String value) {
		Long result = this.stringRedisTemplate.opsForList().rightPushAll(key, new String[]{value});
		return result;
	}

	/**
	 * 获取锁（非阻塞）
	 * @param key
	 * @param expire 过期时长（单位：毫秒）
	 * @return
	 */
	public boolean lock(String key, long expire) {
		long newExpireTime = new Date().getTime() + expire +1000;
		boolean e = this.stringRedisTemplate.boundValueOps(key).setIfAbsent(String.valueOf(newExpireTime)).booleanValue();
		if (e) {
			return true;
		}
		long oldExpireTime = 0;
		try {
			String time = this.stringRedisTemplate.boundValueOps(key).get();
			
			int i  = 0;
			while(time == null) {
				newExpireTime = new Date().getTime() + expire +1000;
				e = this.stringRedisTemplate.boundValueOps(key).setIfAbsent(String.valueOf(newExpireTime)).booleanValue();
				if (e) {
					return true;
				}else {
					time = this.stringRedisTemplate.boundValueOps(key).get();
					if(time == null) {
						i ++;
						if(i >= 2) {
							return false;
						}
					}
				}
			}
			oldExpireTime = Long.parseLong(time);
		} catch (NumberFormatException e1) {
			log.error(e1.getMessage() ,e1);
			this.stringRedisTemplate.delete(key);
			return false;
		}
		
		
		if(oldExpireTime < new Date().getTime()) {
			newExpireTime = new Date().getTime() + expire +1000;
			long currentExpireTime = 0;
			
			String currentExpireTimeStr = this.stringRedisTemplate.boundValueOps(key).getAndSet(String.valueOf(newExpireTime));
			if(currentExpireTimeStr == null) {
				newExpireTime = new Date().getTime() + expire +1000;
				e = this.stringRedisTemplate.boundValueOps(key).setIfAbsent(String.valueOf(newExpireTime)).booleanValue();
				if (e) {
					return true;
				}else {
					currentExpireTimeStr = this.stringRedisTemplate.boundValueOps(key).get();
					if(currentExpireTimeStr == null) {
						return false;
					}
				}
			}
			currentExpireTime = Long.parseLong(currentExpireTimeStr);
			
			if(currentExpireTime < new Date().getTime()) {
				return true;
			}
		}
		
		return false;
	}

	/**
	 * 释放锁（非阻塞）
	 * @param key
	 * @return
	 */
	public boolean unlock(String key) {
		
		long oldExpireTime = 0;
		try {
				oldExpireTime = Long.parseLong(this.stringRedisTemplate.boundValueOps(key).get());
				if(oldExpireTime > new Date().getTime()) {
					this.stringRedisTemplate.delete(key);
				}
		} catch (NumberFormatException e) {
			log.error(e.getMessage() ,e);
			this.stringRedisTemplate.delete(key);
		}
		return true;
	}
	
	/**
	 * 获取锁（阻塞）
	 * @param key
	 * @param expire
	 * @return
	 * @throws InterruptedException 
	 */
	public boolean lockBolck(String key, long expire) throws InterruptedException {
		boolean hasLock = false;
		if(!key.startsWith(RedisLockAopConf.LOCK_PREFIX)) {
    		throw new ServiceException(CodeConstant.Lock_100005,messageUtil.getMessage(CodeConstant.Lock_100005));
    	}
    	
    	for (;;) {
    		hasLock  =  this.lock(key,expire);
    		if(hasLock) {
    			return hasLock;
    		}else {
    			Object lock = RedisLockAopConf.lockObjectMap.get(key);
    			if(lock == null) {
    				RedisLockAopConf.lockObjectMap.putIfAbsent(key,new Object());
    				lock = RedisLockAopConf.lockObjectMap.get(key);
    			}
				synchronized (lock) {
    				lock.wait(3000);
    			}
    		}
		}
	}
	
	/**
	 * 释放锁（阻塞）
	 * @param key
	 * @return
	 */
	public boolean unlockBolck(String key) {
		this.unlock(key);
		this.convertAndSend(key, "1");
		return true;
	}
	

	public List<String> listAll(String rediskey) {
		Long size = this.stringRedisTemplate.opsForList().size(rediskey);
		return this.stringRedisTemplate.opsForList().range(rediskey, 0L, size.longValue());
	}

	public void delete(String key) {
		this.stringRedisTemplate.delete(key);
	}

	public Long decr(String key) {
		Long result = Long.valueOf(0L);

		try {
			result = this.redisTemplate.boundValueOps(key).increment(-1L);
		} catch (Exception arg3) {
			log.error("redisTemplate decr error and key = " + key, arg3);
		}

		return result;
	}

	public Long incr(String key) {
		Long result = Long.valueOf(0L);

		try {
			result = this.redisTemplate.boundValueOps(key).increment(1L);
		} catch (Exception arg3) {
			log.error("redisTemplate incr error and key = " + key, arg3);
		}

		return result;
	}

	public Long decrBy(String key, long integer) {
		Long result = Long.valueOf(0L);

		try {
			result = this.redisTemplate.boundValueOps(key).increment(-integer);
		} catch (Exception arg5) {
			log.error("redisTemplate decrBy error and key = " + key, arg5);
		}

		return result;
	}

	public Long incrBy(String key, long integer) {
		Long result = Long.valueOf(0L);

		try {
			result = this.redisTemplate.boundValueOps(key).increment(integer);
		} catch (Exception arg5) {
			log.error("redisTemplate incrBy error and key = " + key, arg5);
		}

		return result;
	}

	public Double decrByFloat(String key, double db) {
		double result = 0.0D;

		try {
			result = this.redisTemplate.boundValueOps(key).increment(-db).doubleValue();
		} catch (Exception arg6) {
			log.error("redisTemplate decrByFloat error and key = " + key, arg6);
		}

		return Double.valueOf(result);
	}

	public Double incrByFloat(String key, double db) {
		double result = 0.0D;

		try {
			result = this.redisTemplate.boundValueOps(key).increment(db).doubleValue();
		} catch (Exception arg6) {
			log.error("redisTemplate incrByFloat error and key = " + key, arg6);
		}

		return Double.valueOf(result);
	}

	public Object get(String key) {
		return this.redisTemplate.boundValueOps(key).get(0L, -1L);
	}
	
	public void convertAndSend(String channel,Object message) {
		this.stringRedisTemplate.convertAndSend(channel, message);
	}
}